package textcvt;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import util.XmlNodeUtil;
/**
 * 
 * @author Bing
 * @version 1.0.1
 */
public class RuleScript {
	public static final String BEGIN = "begin";
	public static final String END = "end";
	public static final String FILTER = "filter";
	public static final String ACTION = "action";
	
	private ScriptNode begin = null;
	private ScriptNode end = null;
	private ArrayList<ScriptNode> filters=new ArrayList<ScriptNode>();
	private HashMap<String,ScriptNode> actions=new HashMap<String,ScriptNode>();

	public RuleScript(InputStream ruleContent){
		InputStream in = null;
		Document doc = null;
		try {
			in = new BufferedInputStream(ruleContent);
			doc = XmlNodeUtil.buildDOM(in);
		} catch (Exception e) {			
			e.printStackTrace();
		}
		
		try {
			if(in!=null)
				in.close();
		} catch (IOException e) {
			e.printStackTrace();
		}	
		
		for(Node n=doc.getDocumentElement().getFirstChild(); n!=null; n=n.getNextSibling()){
			if(n.getNodeType()!=Node.ELEMENT_NODE) continue;
			if(!XmlNodeUtil.getNodeAttrBooleanValue(n, ScriptNode.ATTR_USED, true, false))
				continue;
			String tag = n.getNodeName();
			if(tag.equalsIgnoreCase(BEGIN)){
				this.begin = ScriptNode.getScriptNode(n);
			}else if(tag.equalsIgnoreCase(END)){
				this.end = ScriptNode.getScriptNode(n);
			}else if(tag.equalsIgnoreCase(FILTER)){
				ScriptNode sn = ScriptNode.getScriptNode(n);
				if(!sn.getNodeValue().isEmpty())
					this.filters.add(sn);			
			}else if(tag.equalsIgnoreCase(ACTION)){
				ScriptNode sn = ScriptNode.getScriptNode(n);
				if(!sn.getNodeValue().isEmpty())
					this.actions.put(sn.getAttribute(ScriptNode.ATTR_NAME), sn);
			}
		}
	}
	
	public ScriptNode getBegin(){
		return this.begin;
	}
	
	public ScriptNode getEnd(){
		return this.end;
	}
	
	public List<ScriptNode> getFilters(){
		return this.filters;
	}
	
	public ScriptNode getAction(String name){
		return name==null||name.isEmpty()? null:this.actions.get(name);
	}
	
	static String getNodeValue(Node node){
		Node vNode = null;
		for(Node n=node.getFirstChild(); n!=null; n=n.getNextSibling()){
			if(n.getNodeType()!=Node.CDATA_SECTION_NODE) continue;
			vNode=n;
			break;
		}
		return vNode==null?node.getTextContent().trim():vNode.getNodeValue().trim();
	}
	
	public static class ScriptNode {
		public static final String ATTR_ACTION = "action";
		public static final String ATTR_NAME = "name";
		public static final String ATTR_USED = "used";
		private String tag = null;
		private String text = null;
		private HashMap<String, String> attribs= null;
		
		/**
		 * get ScriptNode from xml DOM node
		 * @param node
		 * @return
		 */
		static ScriptNode getScriptNode(Node node){
			ScriptNode n = new ScriptNode();
			n.tag = node.getNodeName();
			n.text = RuleScript.getNodeValue(node);
			if(node.hasAttributes()){
				NamedNodeMap m = node.getAttributes();
				n.attribs = new HashMap<String,String>(m.getLength());
				for(int i=0;i<m.getLength();i++){
					Node a = m.item(i);
					n.attribs.put(a.getNodeName(), a.getNodeValue());
				}
			}
			return n;
		}
		
		public String getNodeName(){
			return this.tag;
		}
		
		public String getNodeValue(){
			return this.text;
		}
		
		public String getAttribute(String key){
			return this.attribs==null?null:this.attribs.get(key);
		}
	}
	
	public static class Pattern {
		public static final int CASE_INSENSITIVE = 0x02;
		public static final int DOTALL = 0x20;
		
		private Matcher matcher = new Matcher(this);
		private PatternNode[] content;
		
	    public static Pattern compile(String regex) {
	        return compile(regex, 0);
	    }
		public static Pattern compile(String regex, int flags){
			Pattern p = new Pattern();
			ArrayList<PatternNode> list = new ArrayList<PatternNode>(4);
			int last = 0;
			for(int index = 0; (index=regex.indexOf('*', index))!=-1; index++){
				if(index==0||regex.charAt(index-1)!='\\'){
					PatternNode pn = new PatternNode();
					pn.desc = regex.substring(last, index).replace("\\*", "*");
					list.add(pn);
					pn = new PatternNode();
					pn.type = PatternNode.ALL;
					list.add(pn);
					last = index + 1;
				}				
			}
			
			PatternNode pn = new PatternNode();
			pn.desc = regex.substring(last, regex.length()).replace("\\*", "*");
			list.add(pn);
			
			p.content = list.toArray(new PatternNode[0]);
			return p;
		}
		
		public Matcher matcher(CharSequence input){
			Matcher matcher = new Matcher(this);
			matcher.text = input;
			matcher.from = 0;
			matcher.to = input.length();
			return matcher;
		}
		
		/**
		 * 
		 * @param input
		 * @param offset
		 * @return -1 not matcher, else the end index
		 */
		public int matcher(CharSequence input, int offset){
			matcher.text = input;
			matcher.from = offset;
			matcher.to = input.length();
			return matcher.lookingAt()? matcher.end():-1;
		}
	}
	
	private static class PatternNode{
		static final int ALL = 1;
		int type = 0;
		String desc = null;
	}
	
	public static class Matcher{
		int start = 0;
		int end = 0;
		int next= 0;
		
		CharSequence text = null;
		int from = 0;
		int to;		
		Pattern pattern = null;
				
		public Matcher(Pattern pattern){
			this.pattern = pattern;
		}
		
		public int start(){
			return start;
		}
		public int end(){
			return end;
		}
		public Matcher region(int start, int end){
			this.from = start;
			this.to = end;
			return this;
		} 
		public boolean find(){
			for(next=from;next<to;next++){
				if(startsWithPattern(next))
					return true;
			}
			return false;
		}
		
		public boolean lookingAt(){
			return startsWithPattern(from);
		}
		
		private boolean startsWithPattern(int offset){
			boolean ret = pattern.content.length>0;
			int pos = offset;
			for(int i=0;i<pattern.content.length;i++){
				if(pattern.content[i].type==PatternNode.ALL){
					continue;
				}else{	
					int p;
					if(i==0 && startsWith(text,pos,to,pattern.content[i].desc)){
						pos += pattern.content[i].desc.length();
					}else if(i>0 && (p=indexOf(text,pos,to,pattern.content, i))!=-1){
						pos=p + pattern.content[i].desc.length();
					}else{
						ret = false;
						break;
					}
				}
			}
			
			if(ret){
				start = offset;
				end = pattern.content[pattern.content.length-1].type==PatternNode.ALL? to:pos;
			}
			return ret;
		}

		private static int indexOf(CharSequence arr, int from, int to, PatternNode[] cnt, int cnt_index){
			for(int i=from;i<to;i++){
				if(startsWith(arr,i,to,cnt[cnt_index].desc))
					return i;
				for(int j=cnt.length-1; j>cnt_index; j--){
					if(cnt[j].type==PatternNode.ALL)
						continue;
					if(startsWith(arr,i,to,cnt[j].desc))
						return -1;
				}
			}
			return -1;
		}

		private static boolean startsWith(CharSequence arr, int from, int to, String str){	
			int strlen=str.length();
			if(to-from<strlen)
				return false;
			for(int i=0; i<strlen; i++){
				if(Character.toLowerCase(arr.charAt(i+from))!=Character.toLowerCase(str.charAt(i)))
					return false;
			}
			return true;
		}
	}
}
